package tileStyle;

import tileStyle.Prism.*;
import tileStyle.Prism.events.*;
import java.util.*;

/**
 * @author Yuriy Brun
 * 
 * A Tile represents a tile style tile.  It has a type, up to 4 neighbors, 
 * its assembly ID number, and a host node on which it's deployed.  
 * A Tile is mutable.
 * 
 * @param TileType type : the type of this tile.
 * @param Tile[] neighbors : the neighbor tiles of this tile.  If a neighbor is unknown or 
 *                           does not exist, then that element is set to null.
 * @param Tile[] parentNeighbors : the neighbors of the parent of this tile.  If a 
 * 								   parent did not have a neighbor, then that 
 * 								   element is set to null.
 * @param Node host : The node on this this tile is deployed.
 * @param int assemblyID: an ID unique to each assembly.
 * @param Tile child0, child1 : the two children of this tile, with the assemblyIDs 
 *                              assemblyID * 2 and assemblyID * 2 + 1, respectively.
 * @param boolean isSeed : indicates that this tile is part of a seed
 * @param boolean replicated : indicates whether this tile has replicated.
 * @param boolean recruited : indicates whether this tile has recruited.  
 * 
 * @extends Prism.core.Component
 *
 */
public class Tile {
	
//	protected boolean flag;
//	public static final long serialVersionUID = 1L;
	public static final int UNREPLICATED = 0;
	public static final int UPDATING = 1;
	public static final int READY_TO_REPLICATE = 2;
	public static final int REPLICATED = 3;
	public static final int RECRUITING  = 4;
	public static final int RECRUITED = 5;
	
	public static final int NUMBER_OF_CHILDREN = 1;

	protected TileType type;
//	protected Tile[] neighbors;
//	protected Tile[] parentNeighbors;
	protected Node host;
	protected int assemblyID;
	protected int inNodeID;
	
	protected boolean isSeed;
//	protected boolean replicated;
//	protected boolean recruited;
	
	protected volatile int status;
	
//	protected ExtensiblePort[] children;
//	protected ExtensiblePort parent;
	protected TileAddress[] children;
//	protected Address parent;
	
//	protected ExtensiblePort[] neighbors;
	protected TileAddress[] neighborsAddress;
	// A parentNeighbors port points to the neighbors of your parents
//	protected ExtensiblePort[] parentNeighbors;
	protected TileAddress[] parentNeighborsAddress;
	// A neighborChild port points to the children of your neighbors
//	protected ExtensiblePort[] neighborChild;
//	protected Address[] neighborChildAddess;
	
	protected List<ChildUpdateResponsePacket> childResponses;
	
	/*
	 * @param String name : the name of this tile.
	 * Creates a new Tile with the given name.  This constructor is only here 
	 * for the superclass.
	 */
	private Tile() {
		initializePorts();
	}

/*
	 * @param String name : the name of this tile.
	 * @param AbstractImplementation implementation : an implementation
	 * Creates a new Tile with the given name and implementation.  
	 * This constructor is only here for the superclass.
	private Tile(String name, AbstractImplementation implementation) {
		super(name, implementation);
		initializePorts();
	}
*/
	
//	private void initializePort(ExtensiblePort p) {
//		p.addDistributionModule(host.getSocketDistribution());
//		p.scaffold = host.getScaffold();
//		host.getArchitecture().add(p);
//		this.addCompPort(p);
//	}
	
	private void initializePorts() {
//		parent = new ExtensiblePort("Parent Port", PrismConstants.REPLY);
//		children = new ExtensiblePort[2];
//		children[0] = new ExtensiblePort("Child0 Port", PrismConstants.REQUEST);
//		children[1] = new ExtensiblePort("Child1 Port", PrismConstants.REQUEST);
		
//		initializePort(parent);
//		initializePort(children[0]);
//		initializePort(children[1]);
		
		children = new TileAddress[NUMBER_OF_CHILDREN];
		neighborsAddress = new TileAddress[4];
		parentNeighborsAddress = new TileAddress[4];
//		for (int i = 0; i < 4; i++) {
//			neighbors[i] = new ExtensiblePort("Neighbor Port" + i, PrismConstants.REQUEST_REPLY);
//			parentNeighbors[i] = new ExtensiblePort("Parent Neighbor Port" + i, PrismConstants.REQUEST);
//			neighborChild[i] = new ExtensiblePort("Neighbor Child Port" + i, PrismConstants.REPLY);
//			initializePort(neighbors[i]);
//			initializePort(parentNeighbors[i]);
//			initializePort(neighborChild[i]);
//		}
	}
	
	/*
	 * This constructor should never be used by the user.  
	 * The user should use Node.deployTile(...) method to create new Tile objects.
	 * @param String name : the name of this tile.
	 * @param TileType type : the type of this tile.
	 * @param Node host : the Node that deploys this tile.
	 * @param int assemgblyID : this tile's assemblyID.  
	 * @param boolean isSeed : indicates whether this is part of a seed.
	 * @param Port parent : this Tile's parent's child Port
	 */
	public Tile(TileType type, Node host, int assemblyID, boolean isSeed) {
		this();
		
//		flag = false;
		
		this.type = type;
		this.host = host;
		this.assemblyID = assemblyID;
		if (isSeed)
			status = UNREPLICATED;
		else
			status = REPLICATED;
		
		this.isSeed = isSeed;
		
		childResponses = Collections.synchronizedList(new ArrayList<ChildUpdateResponsePacket>());
		
//		this.inNodeID = inNodeID;
//		replicated = false;
//		recruited = false;
//		this.isSeed = isSeed;
//		this.parent.connect(parent.getHostname(), parent.getPort());
//		this.parent = parent;
	}
	
	public int getStatus() {
		return status;
	}
	
	public void setStatus(int newStatus) {
		status = newStatus;
	}
	
	public void addChildUpdateResponsePacket(ChildUpdateResponsePacket packet) {
		childResponses.add(packet);
	}
	
	public void setInNodeID(int inNodeID) {
		this.inNodeID = inNodeID;
	}
	
	public int getInNodeID() {
		return inNodeID;
	}
	
	public TileAddress getAddress() {
		NodeAddress hostAddress = host.getAddress();
		return new TileAddress(hostAddress.getHostname(), 
				           hostAddress.getUniqueID(),
				           hostAddress.getPort(),
				           inNodeID);
	}

/*
	 * This constructor should never be used by the user.  
	 * The user should use Node.deployTile(...) method to create new Tile objects.
	 * @param String name : the name of this tile.
	 * @param AbstractImplementation implementation : an implementation
	 * @param TileType type : the type of this tile.
	 * @param Node host : the Node that deploys this tile.
	 * @param int assemgblyID : this tile's assemblyID.  
	 * @param boolean isSeed : indicates whether this is part of a seed.
	 * @param Port parent : a port to this Tile's parent
	public Tile(String name, AbstractImplementation implementation, TileType type, Node host, int assemblyID, boolean isSeed, Address parent) {
		this(name, implementation);	
		this.type = type;
		this.host = host;
		this.assemblyID = assemblyID;
		replicated = false;
		recruited = false;
		this.isSeed = isSeed;
//		this.parent.connect(parent.getHostname(), parent.getPort());
		this.parent = parent;
	}
*/
	
	/*
	 * @param Tile child0, child1 : the two children of this tile
	 * If this tile's children were blank, sets the children of this Tile
	 * otherwise, throws a TileTypeException
	 * @throws TileTypeException if the children are non-null 
	 */
	public void setChild(TileAddress child, int childNum) throws TileStyleException {
		if(childNum >= NUMBER_OF_CHILDREN)
			throw new TileStyleException("Requested to set a child with index greater than the allowed number of children");
		if (children[childNum] != null)
			throw new TileStyleException("Tile " + this + " is trying to set children despite already having them");

//		children[0].connect(child0.getHostname(), child0.getPort());
//		children[1].connect(child1.getHostname(), child1.getPort());
		children[childNum] = child;
		
		for (int i = 0 ; i < childResponses.size(); i++) {
			ChildUpdateResponsePacket packet = childResponses.get(i);
			if (childNum == packet.getChildNum()) {
				ChildUpdateResponse event = packet.getEvent();
				event.setChild(child);
				host.send(event, packet.getPort());
			}
		}
	}
	
	public TileAddress getChild(int childNum) {
		return children[childNum];
	}
	
/*
	 * @returns the Address to this Tile's parent 
	 
	public Address getParentPort() {
		return parent;
	}
*/	

	
	/*
	 * @param Tile neighbor : this Tile's neighbor
	 * @param int direction : the direction of the neighbor
	 * Sets this Tile's neighbor on the direction side.
	 */
//	public void setNeighbor(Tile neighbor, int direction){
//		neighbors[direction] = neighbor;
	public void setNeighbor(TileAddress neighbor, int direction){
//		neighbors[direction].connect(neighbor.getHostname(), neighbor.getPort());
		if ((neighborsAddress[direction] != null) && (neighbor == null))
			throw new TileStyleException("Erasing a neighbor!!!");
		neighborsAddress[direction] = neighbor;
	}
	
	/*
	 * @param int direction : the side of a desired neighbor
	 * @returns the neighbor on the direction side.
	 */
//	public Tile getNeighbor(int direction){
//		return neighbors[direction];
	public TileAddress getNeighbor(int direction) {
		return neighborsAddress[direction];
	}
	
	/*
	 * @param Tile parantNeighbor : this Tile's parent neighbor
	 * @param int direction : the direction of the parent neighbor
	 * Sets this Tile's parentNeighbor on the direction side.
	 */
//	public void setParentNeighbor(Tile parentNeighbor, int direction){
//		parentNeighbors[direction] = parentNeighbor;
	public void setParentNeighbor(TileAddress parentNeighbor, int direction){
//		parentNeighbors[direction].connect(parentNeighbor.getHostname(), parentNeighbor.getPort());
		parentNeighborsAddress[direction] = parentNeighbor;
	}
	
	/*
	 * @param int direction : the side of a desired parent neighbor
	 * @returns the parent neighbor on the direction side.
	 */
	public TileAddress getParentNeighbor(int direction) {
		return parentNeighborsAddress[direction];
	}
	
	/*
	 * @returns this Tile's assemblyID.
	 */
	public int getAssemblyID(){
		return assemblyID;
	}
	
	/*
	 * @returns this Tile's host.
	 */
	public Node getHost(){
		return host;
	}
	
	/*
	 * @returns this Tile's type.
	 */
	public TileType getType(){
		return type;
	}
	
//	public boolean isSeed() {
//		return isSeed;
//	}
	
	/*
	 * @returns true iff this Tile is ready to replicate.  That is,
	 * all of the sides that have parentNeighbors have been assigned 
	 * neighbors.
	 */
	public boolean isReadyToReplicate() {
		for (int i = 0; i < 4; i++)
			if ((parentNeighborsAddress[i] != null) && (neighborsAddress[i] == null)) 
				return false;
		return true;
	}
	
	/*
	 * Designates this Tile as having completed replication.  
	 */
//	public void setReplicated(boolean replicated) {
//		this.replicated = replicated;
//	}

	
	/*
	 * @returns true iff this tile has completed replication.  
	 */
//	public boolean hasReplicated() {
//		return replicated;
//		return ((children[0] != null) && (children[1] != null));
//	}
	
	/*
	 * @returns true iff this Tile is ready to recruit.  That is,
	 * it has a north and a west neighbor.  
	 */
	public boolean isReadyToRecruit() {
		return ((neighborsAddress[TileType.NORTH] != null) && (neighborsAddress[TileType.WEST] != null));
	}
	
	/*
	 * Designates this Tile as having completed recruiting.  
	 */
//	public void setRecruited() {
//		recruited = true;
//	}
	
	/*
	 * @returns true iff this Tile has completed recruiting.  
	 */
//	public boolean hasRecruited() {
//		return recruited;
//	}
	
	/*
	 * @param asseblyID : the assemblyID of a desired child.
	 * @returns the desired child of this Tile if its assemblyID matches and 
	 * null otherwise.    
	 */
/*	private Tile getChild(int assemblyID) {
		if (assemblyID == this.assemblyID * 2)
			return child0;
		if (assemblyID == this.assemblyID * 2 + 1)
			return child1;
		return null;
	}
*/	
	
	/*
	 * Updates the neighbors by checking if the parent neighbors have created the appropriate 
	 * children, which can then become this Tile's neighbors.  
	 */
	public void updateNeighbors() {
//		System.out.println("Updating neighbors");
		for (int i = 0; i < 4; i++)
			if ((parentNeighborsAddress[i] != null) && (neighborsAddress[i] == null)) {
				host.updateNeighbor(parentNeighborsAddress[i], assemblyID, i, inNodeID);
				// send an event to parentNeighbor asking for child
//				ChildUpdateRequest event = new ChildUpdateRequest(assemblyID, i);
//				send(event, parentNeighbors[i]);
//				neighbors[i] = parentNeighbors[i].getChild(assemblyID);
			}
	}
	
/*
	 * Handles two kinds of events: ChildUpdateRequest and ChildUpdateResponse

	public void handle(Event event) {
		if (event instanceof ChildUpdateRequest) {
			ChildUpdateRequest e = (ChildUpdateRequest) event;
			if (e.getAssemblyID() == assemblyID * 2)
				send(new ChildUpdateResponse(assemblyID * 2, children[0], e.getDirection()), e.getReplyTo());
			if (e.getAssemblyID() == assemblyID * 2 + 1)
				send(new ChildUpdateResponse(assemblyID * 2 + 1, child1, e.getDirection()), e.getReplyTo());
		}
		else if (event instanceof ChildUpdateResponse) {
			ChildUpdateResponse e = (ChildUpdateResponse) event;
			if (e.getAssemblyID() == assemblyID)
				setNeighbor(e.getChildPort(), e.getDirection());
		}
	}
*/

}
